package org.opens.lambda.optional;

import org.junit.Test;
import org.opens.lambda.entity.BeanOne;
import org.opens.lambda.entity.BeanThree;
import org.opens.lambda.entity.BeanTwo;

import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;

public class OptionalExample {

    /**
     * 功能:
     *      测试下面的四种获取方式.
     * 输出:
     *      捕获了空指针异常.
     *      UNKNOW
     *      UNKNOW
     *      UNKNOW
     *      s1
     *      s1
     *      s1
     *      s1
     * 说明:
     *      1. 可以发现, 同样的输出结果中, Optional的代码最为优雅.
     *      2. 其实这种思路的出发点就是为了:
     *          - 一个方法在返回值的时候尽量不要返回null, 如果返回字符串就用""来替换无结果时的状态, 如果返回List, 就用空的List来代替.
     */
    @Test
    public void test1() {
        BeanOne beanOne = new BeanOne();
        BeanOne beanOne2 = new BeanOne();
        BeanTwo beanTwo = new BeanTwo();
        BeanThree beanThree = new BeanThree();

        beanThree.setName("s1");
        beanTwo.setBeanThree(beanThree);
        beanOne2.setBeanTwo(beanTwo);

        try {
            System.out.println(getBeanThreeName(beanOne));
        } catch (NullPointerException e) {
            System.out.println("捕获了空指针异常.");
        }
        System.out.println(getBeanThreeName2(beanOne));
        System.out.println(getBeanThreeName3(beanOne));
        System.out.println(getBeanThreeNameUseOptional(beanOne));

        try {
            System.out.println(getBeanThreeName(beanOne2));
        } catch (NullPointerException e) {
            System.out.println("捕获了空指针异常.");
        }
        System.out.println(getBeanThreeName2(beanOne2));
        System.out.println(getBeanThreeName3(beanOne2));
        System.out.println(getBeanThreeNameUseOptional(beanOne2));
    }

    /**
     * 功能:
     *      根据BeanOne获取到BeanTwo, 在获取到BeanThree, 然后获取BeanThree的name.
     * 说明:
     *      1. 由于没有进行初始化, 这个方法时必然报错空指针异常的.
     * @param beanOne   初始对象.
     * @return          BeanThree的name值.
     */
    public static String getBeanThreeName(BeanOne beanOne) {
        return beanOne.getBeanTwo().getBeanThree().getName();
    }

    /**
     * 功能:
     *      这种方式是对getBeanThreeName方法逻辑的一种优化, 每次在获取之前先判断, 就可以避免空指针异常.
     * 说明:
     *      1. 但是这样会有一种问题:
     *          - 如果嵌套的层数过高, 代码写出来会很难看;
     *          - 人们是不喜欢阅读多重if嵌套的.
     * @param beanOne   初始参数
     * @return          BeanThree的name值.
     */
    public static String getBeanThreeName2(BeanOne beanOne) {
        if(beanOne != null) {
            BeanTwo beanTwo = beanOne.getBeanTwo();
            if(beanTwo != null) {
                BeanThree beanThree = beanTwo.getBeanThree();
                if(beanThree != null) {
                    return beanThree.getName();
                }
            }
        }
        return "UNKNOW";
    }

    /**
     * 功能:
     *      这是对getBeanThreeName2的一种再次优化, 使代码更清晰.
     * 说明:
     *      1. 其实就是分解了多层if嵌套, 个人感觉多层if嵌套并没有自顶向下看着舒服.
     * @param beanOne   参数
     * @return          BeanThree的name.
     */
    public static String getBeanThreeName3(BeanOne beanOne) {
        final String NO_MATCHING_RESULTS = "UNKNOW";
        if(beanOne == null) {
            return NO_MATCHING_RESULTS;
        }
        BeanTwo beanTwo = beanOne.getBeanTwo();
        if(beanTwo == null) {
            return NO_MATCHING_RESULTS;
        }
        BeanThree beanThree = beanTwo.getBeanThree();
        if(beanThree == null) {
            return NO_MATCHING_RESULTS;
        }
        return beanThree.getName();
    }

    /**
     * 说明:
     *      1. 其实这种方式就是利用map层层提取将需要的值封装为一个新的Optional, 最后再使用orElse判断.
     *      2. 查看源码会发现, flatMap方法和map的方法基本相同, 只是要求你传入的方法的返回值是flatMap, 而map会自动帮你做这个(将结果集处理成Optional).
     *          - 所以感觉flatMap应该会成为一个启用的方法.
     * @param beanOne   参数.
     * @return          BeanThree的name.
     */
    public static String getBeanThreeNameUseOptional(BeanOne beanOne) {
        final String NO_MATCHING_RESULTS = "UNKNOW";
        return Optional.ofNullable(beanOne)
                .map(BeanOne::getBeanTwo)
                .map(BeanTwo::getBeanThree)
                .map(BeanThree::getName)
                .orElse(NO_MATCHING_RESULTS);
    }


    /**
     * 说明:
     *      1. 其实对比可以发现, flatMap也不是一无是处的, 它可以使过程更清晰, 但是flatMap的学习曲线挺尴尬的:
     *          - 开始学习时, map的使用率比较大, 但是在理解了map的用法之后, 会被flatMap的名字所困惑, 会疑问"什么是扁平化处理?", 相对于mao来说, flatMap更加易于理解, 但是使用方式较为繁琐, 而map
     *          的使用方式较为简便, 但是不太容易理解, 所以肯定会造成人们之后都会去用map而不用flatMap的情况.
     * @param beanOne   参数.
     * @return          BeanThree的name.
     */
    public static String getBeanThreeNameUseOptionalWithFlatMap(BeanOne beanOne) {
        final String NO_MATCHING_RESULTS = "UNKNOW";
        return Optional.ofNullable(beanOne)
                .flatMap(x -> Optional.ofNullable(x.getBeanTwo()))
                .flatMap(y -> Optional.ofNullable(y.getBeanThree()))
                .flatMap(z -> Optional.ofNullable(z.getName()))
                .orElse(NO_MATCHING_RESULTS);
    }

    @Test
    public void testUseGetBeanThreeNameUseOptionalWithFlatMap() {
        System.out.println(getBeanThreeNameUseOptionalWithFlatMap(null));
    }

    /**
     * 说明:
     *      Optional有三种延伸类: OptionalInt, OptionalDouble, OptionalLong; 其实就是将Optional的泛型换成了指定的类型.
     */
    @Test
    public void optionalIntAndOptionalDoubleAndOptionalLong() {
        OptionalInt optionalInt = OptionalInt.of(23);
        int asInt = optionalInt.getAsInt();
        System.out.println(asInt);

        OptionalDouble optionalDouble = OptionalDouble.of(2.25d);
        System.out.println(optionalDouble.getAsDouble());

        OptionalLong optionalLong = OptionalLong.of(23L);
        System.out.println(optionalLong.getAsLong());
    }

    @Test
    public void testMethod_getBeanThreeNameUseOptional() {
        String beanThreeNameUseOptional = getBeanThreeNameUseOptional(null);
        System.out.println(beanThreeNameUseOptional);

        BeanOne beanOne = new BeanOne();
        BeanTwo beanTwo = new BeanTwo();
        beanOne.setBeanTwo(beanTwo);
        System.out.println(getBeanThreeNameUseOptional(beanOne));
    }



}
